﻿using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Affinity.Synthesis.ExpressionCells.Basis;

namespace Affinity.Synthesis.ExpressionCells
{
    /// <summary>
    /// Implements IExpressionCell for an expression cell composed of other expression cells. Simplifies usage of
    /// an expression cell tree by exposing its output and all its inputs in one place.
    /// </summary>
    internal class CompositeExpressionCell : IExpressionCell
    {
        /// <summary>
        /// Constructor accepting the final expression cell in a tree and all the parameters feeding that tree.
        /// </summary>
        /// <param name="finalCell"></param>
        /// <param name="parameters"></param>
        public CompositeExpressionCell(IExpressionCell finalCell, params ParameterExpressionCell[] parameters)
        {
            _FinalCell = finalCell;
            _Parameters = parameters;
        }

        /// <summary>
        /// Private parameterless constructor used only when the expression cell copies itself.
        /// </summary>
        private CompositeExpressionCell()
        {
        }

        /// <summary>
        /// Gets or sets the ExpressionCellValues representing the inputs.
        /// </summary>
        public ExpressionCellValue[] Inputs
        {
            get { return _Parameters.Select(parameter => parameter.Input).ToArray(); }
            set { foreach (var parameter in _Parameters) parameter.Input = GetArgumentForParameter(value, parameter); }
        }

        /// <summary>
        /// Gets an empty set of metadata.
        /// </summary>
        public object[] Metadata { get { return new object[0]; } }

        /// <summary>
        /// Gets the ExpressionCellValue representing the output.
        /// </summary>
        public ExpressionCellValue Output
        {
            get { return _FinalCell.Output; }
        }

        private static ExpressionCellValue GetArgumentForParameter(ExpressionCellValue[] inputs, ParameterExpressionCell parameter)
        {
            return inputs[parameter.Ordinal - 1];
        }

        /// <summary>
        /// Creates a copy of this expression cell.
        /// </summary>
        /// <returns></returns>
        public IExpressionCell Copy()
        {
            List<ParameterExpressionCell> newParameterCells = new List<ParameterExpressionCell>();
            IExpressionCell newFinalCell = CopyCellRecursive(_FinalCell, newParameterCells);

            CompositeExpressionCell newCell = new CompositeExpressionCell()
                {
                    _FinalCell = newFinalCell,
                    _Parameters = newParameterCells.ToArray()
                };
            
#if DEBUG
            // For debugging purposes, uncomment the following two lines to output a graph of the expression cell tree represented by this CompositeExpressionCell.
            //TreeGraph treeGraph = TreeGraph.ConstructFromFractal(newFinalCell, parentCell => parentCell.Inputs.Where(input => input != null).Select(input => input.OwningExpressionCell));
            //Debug.WriteLine("\r\n=====================\r\nCopied expression cell tree. Output:\r\n\r\n{0}\r\n=====================\r\n", GraphTextProjector.ProjectGraph(treeGraph));
#endif
            return newCell;
        }

        private static IExpressionCell CopyCellRecursive(IExpressionCell expressionCell, List<ParameterExpressionCell> newParameters)
        {
            IExpressionCell newExpressionCell = expressionCell.Copy();
            if (newExpressionCell is ParameterExpressionCell) newParameters.Add((ParameterExpressionCell)newExpressionCell);
            newExpressionCell.Inputs = expressionCell.Inputs.Select(input => CopyCellRecursive(input.OwningExpressionCell, newParameters).Output).ToArray();
            return newExpressionCell;
        }

        private IExpressionCell _FinalCell = null;
        private ParameterExpressionCell[] _Parameters = null;
    }
}
